#region Copyright
/// <copyright>
/// Copyright (c) 2007 Ilia Shramko.
/// Released under the terms of the GNU General Public License.
/// </copyright>
#endregion

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Net.Mime;
using System.Runtime.InteropServices;
using System.Text;
using System.Web;

namespace OshHost.Web
{
	internal class OshProtocol : OSH.IProtocolSink
	{
		private const int MaxNumberOfRedirects = 10;
		private static string _userAgent = null;

		[DllImport("urlmon.dll", CharSet=CharSet.Ansi)]
		public static extern unsafe int ObtainUserAgentString(
			int options, StringBuilder userAgent, [In] ref int userAgentSize);
		 

		private OSH.ProtocolClass _oshProtocol;
		private TraceSource _traceSource;

		static OshProtocol()
		{
			StringBuilder sb = new StringBuilder(10000);
			int size = sb.Capacity;
			if(ObtainUserAgentString(0, sb, ref size) == 0)
				_userAgent = sb.ToString();
		}

		public OshProtocol()
		{
			_traceSource = new TraceSource("OshHost.Web.OshProtocol");
		}

		public void Register()
		{
			_oshProtocol = new OSH.ProtocolClass();
			_oshProtocol.Register(true, true, null, this);
			//_oshProtocol.Register(true, true, Configuration.Current.HtmlRootFolder, this);
		}

		#region IProtocolSink Members
		public void ProcessRequest(string verb, string uri, string requestHeaders, Array requestBody,
			out string redirectUri, out string usedRequestHeaders,
			out uint statusCode, out string statusDescription,
			out string responseHeaders, out Array responseBody,
			out string cacheFileName)
		{
			string originalUri = uri;
			redirectUri = null;
			HttpRequestResponse requestResponse = null;
			for(int i = 0; i < MaxNumberOfRedirects; i++)
			{
				requestResponse = new HttpRequestResponse(verb, uri, requestHeaders, (requestBody as byte[]));
				if(_userAgent != null && requestResponse.RequestHeaders[HttpHeaders.HeaderUserAgent] == null)
					requestResponse.RequestHeaders.Add(HttpHeaders.HeaderUserAgent, _userAgent);

				_traceSource.TraceInformation("{0} {1}", verb, uri);

				DateTime startTime = DateTime.UtcNow;
				HttpRuntime.ProcessRequest(requestResponse);
				DateTime endTime = DateTime.UtcNow;
				
				_traceSource.TraceInformation("{0} ({1}ms)",
					requestResponse.ResponseStatus,
					Math.Round(endTime.Subtract(startTime).TotalMilliseconds).ToString());

				if(requestResponse.ResponseStatusCode != 302)
					break;

				string location = requestResponse.ResponseHeaders[
					HttpWorkerRequest.GetKnownResponseHeaderName(HttpWorkerRequest.HeaderLocation)];
				if(string.IsNullOrEmpty(location))
					break;

				Uri currentUri;
				if(!Uri.TryCreate(uri, UriKind.Absolute, out currentUri))
					break;

				Uri newUri;
				if(!Uri.TryCreate(location, UriKind.Absolute, out newUri))
				{
					if(!Uri.TryCreate(location, UriKind.Relative, out newUri))
						break;
					
					newUri = new Uri(currentUri, newUri);
				}

				if(currentUri == newUri)
					break;

				uri = newUri.ToString();
				redirectUri = uri;
			}
			statusCode = (uint)requestResponse.ResponseStatusCode;
			statusDescription = requestResponse.ResponseStatusDescription;
			usedRequestHeaders = requestResponse.RequestHeaders.ToString();
			if(requestResponse.ResponseHeaders["Expires"] != null)
			{
				requestResponse.ResponseHeaders["Date"] = requestResponse.ResponseHeaders["Expires"];
				requestResponse.ResponseHeaders.Remove("Expires");
			}
			responseHeaders = requestResponse.ResponseHeaders.ToString();
			responseBody = requestResponse.ResponseBody;

			// TODO: check cache before processing request
			// TODO: skip caching on nocache response header
			cacheFileName = null;
			Update(uri, originalUri,
				requestResponse.ResponseStatus,
				requestResponse.ResponseHeaders,
				requestResponse.ResponseBody,
				out cacheFileName);
		}
		#endregion IProtocolSink Members

		public void Update(string url, string originalUrl,
			string statusList, HttpHeaders httpHeaders, byte[] body, out string cacheFileName)
		{
			CacheEntry cacheEntry = null;
			if(CacheEntry.TryGetEntry(url, out cacheEntry))
				CacheEntry.Delete(url);
			
			cacheEntry = CreateCacheEntry(url, originalUrl, statusList, httpHeaders, body);
			cacheFileName = cacheEntry.FileName;
		}

		public CacheEntry CreateCacheEntry(string url, string originalUrl,
			string statusLine, HttpHeaders httpHeaders, byte[] body)
		{
			string fileExtension = GetFileExtension(url, httpHeaders[HttpHeaders.HeaderContentType]);

			DateTime expiresTime;
			httpHeaders.GetDateTime(HttpHeaders.HeaderExpires, out expiresTime);

			DateTime lastModifiedTime;
			httpHeaders.GetDateTime(HttpHeaders.HeaderLastModified, out lastModifiedTime);

			string headerInfo = statusLine + "\r\n" + httpHeaders.ToString() + "\r\n";

			CacheEntry cacheEntry = CacheEntry.Create(url, originalUrl, fileExtension,
				expiresTime, lastModifiedTime, headerInfo, body);

			return cacheEntry;
		}

		private string GetFileExtension(string url, string contentTypeStr)
		{
			string mediaType = string.Empty;
			try
			{
				if(!string.IsNullOrEmpty(contentTypeStr))
				{
					ContentType contentType = new ContentType(contentTypeStr);
					mediaType = contentType.MediaType;
				}
			}
			catch(Exception) { }

			switch(mediaType)
			{
				case MediaTypeNames.Text.Html: return "htm";
				case MediaTypeNames.Text.Plain: return "txt";
				case MediaTypeNames.Text.Xml: return "xml";
				case MediaTypeNames.Image.Gif: return "gif";
				case MediaTypeNames.Image.Jpeg: return "jpg";
				case MediaTypeNames.Image.Tiff: return "tiff";
			}

			Uri uri;
			if(Uri.TryCreate(url, UriKind.Absolute, out uri))
			{
				string ext = Path.GetExtension(uri.LocalPath);
				if(!string.IsNullOrEmpty(ext))
					return ext.Substring(1);
			}

			return null;
		}

		//public void Test()
		//{
		//    HttpHeaders httpHeaders = new HttpHeaders();
		//    httpHeaders[HttpHeaders.HeaderExpires] = new DateTime(1, 1, 1, 0, 0, 0, DateTimeKind.Utc).ToString("R");
		//    httpHeaders[HttpHeaders.HeaderLastModified] = new DateTime(2006, 1, 1, 10, 0, 0, DateTimeKind.Utc).ToString("R");

		//    string url = "http://www.attaboy.ru/test/";
		//    byte[] body = Encoding.ASCII.GetBytes("test body");
		//    string cacheFileName;

		//    Update(url, null, "HTTP/1.1 200 OK", httpHeaders, body, out cacheFileName);

		//    CacheEntry entry;
		//    bool result = CacheEntry.TryGetEntry(url, out entry);
		//}
	}
}
